#include "../include/TcpClient.h"
#include "../include/InetAddress.h"
#include "../include/Packet.h"
#include "../include/Logger.h"
#include <unistd.h>
#include <string.h>
#include <stdio.h>

namespace myNameSpace {
TcpClient::TcpClient(): _clientSock(), _sockIO(_clientSock.getFd()) {

}

void TcpClient::connect(unsigned short port, const string &ip) {
    InetAddress serverAddr(port, ip);    
    int ret = ::connect(_clientSock.getFd(), (struct sockaddr *)serverAddr.getInetAddrPtr(), sizeof(serverAddr));
    if (ret == -1) {
        perror("connect");
        exit(EXIT_FAILURE);
    }
    moqi::LOG_INFO("Connect success.");
}

int TcpClient::signup() {
    // 1. 从键盘输入获取用户名并发送给服务器
    string userName = sendUserName(TASK_SIGNUP_SECTION1);
    // 2. 从响应报文中获取服务器生成的setting
    string setting = getSignupSetting();
    int id = -1;
    if (setting.empty()) {
        moqi::LOG_INFO("Signup failed.");
        return id;
    }
    // 3. 从键盘输入获取密码，使用setting加密后将用户名和密文发送给服务器
    sendEncryptedPassword(TASK_SIGNUP_SECTION2, userName, setting);
    // 4. 从响应报文中获取该新用户在数据库中的id
    Packet response;
    readPacket(response);
    if (response._type != TASK_SIGNUP_SECTION2_RESP_OK) {
        moqi::LOG_INFO("Signup failed.");
        return id;
    }
    moqi::LOG_INFO("Signup success.");
    memcpy(&id, response._msg.c_str(), sizeof(int));
    printf("User id: %d\n", id);
    return id;
}

int TcpClient::login() {
    // 1. 从键盘输入获取用户名并发送给服务器
    string userName = sendUserName(TASK_LOGIN_SECTION1);
    // 2. 从响应报文中获取服务器生成的setting
    string setting = getLoginSetting();
    int id = -1;
    if (setting.empty()) {
        moqi::LOG_INFO("Login failed.");
        return id;
    }
    // 3. 从键盘输入获取密码，使用setting加密后将用户名和密文发送给服务器
    sendEncryptedPassword(TASK_LOGIN_SECTION2, userName, setting);
    // 4. 从响应报文中获取该新用户在数据库中的id
    Packet response;
    readPacket(response);
    if (response._type != TASK_LOGIN_SECTION2_RESP_OK) {
        moqi::LOG_INFO("Login failed.");
        return id;
    }
    moqi::LOG_INFO("Login success.");
    memcpy(&id, response._msg.c_str(), sizeof(int));
    printf("User id: %d\n", id);
    return id; // 服务器在数据库中存储的用户名、密文（前缀+盐值+哈希值）元组的id
}

int TcpClient::readStdin(char *buf, int len) {
    bzero(buf, len);
    int ret = ::read(STDIN_FILENO, buf, len);
    if (ret == -1) {
        perror("read");
        exit(EXIT_FAILURE);
    }
    if (buf[ret - 1] == '\n') {
        buf[ret - 1] = '\0';
    }
    moqi::LOG_INFO("Recv %d bytes from stdin: %s.", ret - 1, buf);
    return ret - 1;
}

string TcpClient::sendUserName(TaskType type) {
    printf("请输入用户名：\n");
    char userName[100] = {0};
    readStdin(userName, sizeof(userName));
    sendTLV(type, userName, strlen(userName));
    return userName;
}

string TcpClient::getSignupSetting() {
    Packet packet;
    readPacket(packet);
    if (packet._type != TASK_SIGNUP_SECTION1_RESP_OK) {
        moqi::LOG_INFO("Signup failed.");
        return string();
    }
    moqi::LOG_INFO("Recv setting: %s.", packet._msg.c_str());
    return packet._msg;
}

string TcpClient::getLoginSetting() {
    Packet packet;
    readPacket(packet);
    if (packet._type != TASK_LOGIN_SECTION1_RESP_OK) {
        moqi::LOG_INFO("Login failed.");
        return string();
    }
    moqi::LOG_INFO("Recv setting: %s.", packet._msg.c_str());
    return packet._msg;
}

/* void hexDump(const char* data, int size) { */
/*     for (int i = 0; i < size; i++) { */
/*         printf("%02X ", (unsigned char)data[i]); */
/*         if ((i + 1) % 16 == 0) printf("\n"); */
/*     } */
/*     printf("\n"); */
/* } */

void TcpClient::sendEncryptedPassword(TaskType type, const string &userName, const string &setting) {
    printf("请输入密码：\n");
    char password[512] = {0};
    readStdin(password, sizeof(password));
    char *encryptedPassword = crypt(password, setting.c_str());
    printf("encryptedPassword: %s.\n", encryptedPassword);

    /* char msg[1024] = {0}; */
    /* char *p = msg; */
    /* int userNameLength = userName.size(); */ 
    /* printf("userName: %s, userNameLength: %d\n", userName.c_str(), userNameLength); */
    /* memcpy(p, &userNameLength, sizeof(int)); // userName长度 */
    /* p += sizeof(int); */
    /* printf("msg: %s.\n", msg); */
    /* memcpy(p, userName.c_str(), userNameLength); // userName */
    /* p += userNameLength; */
    /* printf("msg: %s.\n", msg); */
    /* int encryptedPasswordLength = strlen(encryptedPassword); */
    /* printf("encryptedPassword: %s, encryptedPasswordLength: %d\n", encryptedPassword, encryptedPasswordLength); */
    /* memcpy(p, &encryptedPasswordLength, sizeof(int)); // encryptedPassword长度 */
    /* p += sizeof(int); */
    /* printf("msg: %s.\n", msg); */
    /* memcpy(p, encryptedPassword, encryptedPasswordLength); // encryptedPassword */
    /* printf("msg: %s.\n", msg); */
    /* sendTLV(TASK_LOGIN_SECTION2, msg, sizeof(int) + userNameLength + sizeof(int) + encryptedPasswordLength); */
    string msg;
    int userNameLength = userName.size();
    msg += string((char *)&userNameLength, sizeof(int)); // 往msg中追加sizeof(int)个字符，即使中间有'\0'字符也不会停止
    msg += userName;
    int encryptedPasswordLength = strlen(encryptedPassword);
    msg += string((char *)&encryptedPasswordLength, sizeof(int));
    msg += string(encryptedPassword, encryptedPasswordLength);
    /* printf("msg len: %lu\n", msg.size()); */
    sendTLV(type, msg.c_str(), sizeof(int) + userNameLength + sizeof(int) + encryptedPasswordLength);
    /* hexDump(msg.c_str(), msg.size()); */
}

int TcpClient::readPacket(Packet &packet) {
    int ret = _sockIO.readPacket(packet);
    moqi::LOG_INFO("Recv packet, %d bytes, type: %d, length: %d, msg: %s.", ret, packet._type, packet._length, packet._msg.c_str());
    return ret;
}

void TcpClient::sendTLV(TaskType type, const char *msg, int len) {
    TLV tlv;
    tlv._type = type;
    tlv._length = len;
    memcpy(tlv._data, msg, len); // 这里不能用strcpy，因为msg中间可能有'\0'
    if (tlv._length > 0) {
        _sockIO.sendn((const char *)&tlv, sizeof(int) + sizeof(int) + tlv._length);
    }
    moqi::LOG_INFO("Send %u bytes.", sizeof(int) + sizeof(int) + tlv._length);
}

} // end of namespace myNameSpace
